﻿using System;
using System.Configuration;
using System.Reflection;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using Ninject.Activation;

namespace Machinery.Providers
{
    public class NHibernateConfigurationProvider : Provider<NHibernate.Cfg.Configuration>
    {
        // ReSharper disable InconsistentNaming
        private const string MSSQL = "mssql";
        private const string mySQL = "mysql";
        // ReSharper restore InconsistentNaming


        protected virtual void OverrideMappings(AutoPersistenceModel configuration)
        {
            // Customize automappings here, if needed.
            // http://wiki.fluentnhibernate.org/Auto_mapping
        }

        protected virtual void OverrideConfiguration(FluentConfiguration configuration)
        {
            // Customize configuration here, if needed.
            // http://wiki.fluentnhibernate.org/Auto_mapping
        }

        protected override NHibernate.Cfg.Configuration CreateInstance(IContext context)
        {
            if (PersistentAssembly == null)
                throw new ConfigurationErrorsException("No assembly with \"Persistent\" in the name was found. NMachinery projects expects this to be present, containing only data objects. Make sure you have referenced the Persistent assebly from your main assembly. This error might also occur because the referenced assembly has not yet been used. Add a refrence to your persistent library before trying to create the database.");

            var configuration = Fluently.Configure().Database(CreateDatabaseConfiguration());

            // Workaround for “Could not find any resources appropriate for the specified culture or the neutral culture” http://bit.ly/hRorr
            configuration.ExposeConfiguration(c => c.Properties.Add(NHibernate.Cfg.Environment.Hbm2ddlKeyWords, "none"));
            // Show SQL output.
            configuration.ExposeConfiguration(c => c.Properties.Add(NHibernate.Cfg.Environment.ShowSql, "true"));

            OverrideConfiguration(configuration);

            configuration.Mappings(m => m.FluentMappings.AddFromAssembly(PersistentAssembly));
            var autoPersistenceModel = AutoMap.Assembly(PersistentAssembly)
                                        .Where(type => typeof(IPersistentEntity) == (type.GetInterface(typeof(IPersistentEntity).FullName)))
                                        .UseOverridesFromAssembly(PersistentAssembly);
            configuration.Mappings(m => m.AutoMappings.Add(autoPersistenceModel));
            



            var model = new AutoPersistenceModel();
            model.AddEntityAssembly(PersistentAssembly);
            model.Where(type => typeof(IPersistentEntity) == type.GetInterface(typeof(IPersistentEntity).FullName));
            OverrideMappings(model);


            return configuration.BuildConfiguration();
        }

        /// <summary>
        /// Returns the first assembly with the containing "Persistent" in it's name.
        /// </summary>
        private static Assembly PersistentAssembly
        {
            get
            {
                Assembly persistentAssembly = null;
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                    foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
                        if (referencedAssembly.FullName != null && referencedAssembly.FullName.Contains("Persistent"))
                            persistentAssembly = Assembly.Load(referencedAssembly);
                return persistentAssembly;
            }
        }


        /// <summary>
        /// Creates an initial Fluent IPersistenceConfigurer from the database specified in app/web.config. 
        /// Looks for a connectionstring with key "mysql" or "mssql". If not found, it returns a configuration forn an in-memory sqlite db.
        /// </summary>
        /// <returns></returns>
        private static IPersistenceConfigurer CreateDatabaseConfiguration()
        {
            if (ConnectionString(mySQL) != null)
                return MySQLConfiguration.Standard.ConnectionString(ConnectionString(mySQL));
            if (ConnectionString(MSSQL) != null)
                return MsSqlConfiguration.MsSql2005.ConnectionString(ConnectionString(MSSQL));
            
            return SQLiteConfiguration.Standard.InMemory().Raw(global::NHibernate.Cfg.Environment.ReleaseConnections, "on_close");
            
        }

        /// <summary>
        /// Shorthand for connection string from configuration file (app.config/web.config).
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string ConnectionString(string key)
        {
            var connectionStringFromConfig = ConfigurationManager.ConnectionStrings[key.ToLower()];
            return connectionStringFromConfig == null ? null : connectionStringFromConfig.ConnectionString;
        }

        
    }
}